﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.Windows;

namespace PetriNetSharp.Engine
{
	/// <summary>
	/// Delegate for when a place is connected or disconnected from a transition
	/// </summary>
	/// <param name="place">The place that whose connection changes</param>
	public delegate void PlaceConnectionEventHandler(Place place);

	/// <summary>
	/// A logical transition in a petri net
	/// </summary>
	public class Transition : PetriObject
	{
		/// <summary>
		/// Occurs when the TokensToTransfer changes
		/// </summary>
		public event Action TokensToTransferChanged;

		/// <summary>
		/// Occurs when the places connected to this transition changes
		/// </summary>
		public event Action ConnectedPlacesChanged;

		/// <summary>
		/// Occurs when this transition fires
		/// </summary>
		public event Action Fired;

		/// <summary>
		/// Occurs when a place is connected to this transition
		/// </summary>
		public event PlaceConnectionEventHandler PlaceConnected;

		/// <summary>
		/// Occurs when a place is disconnected to this transition
		/// </summary>
		public event PlaceConnectionEventHandler PlaceDisconnected;

		/// <summary>
		/// The collection of Places that go into this Transition
		/// </summary>
		private ObservableCollection<Place> inputPlaces;

		/// <summary>
		/// The collection of Places that go out of this Transition
		/// </summary>
		private ObservableCollection<Place> outputPlaces;

		/// <summary>
		/// The amount of tokens required for this transition to fire,
		/// also the amount of tokens given when this transition fires
		/// </summary>
		private int tokensToTransfer;


		/// <summary>
		/// Create a new Transition
		/// </summary>
		/// <param name="name">The name of the transition</param>
		/// <param name="location">The location of the transition</param>
		/// <param name="uid">The unique ID of the transition</param>
		/// <param name="tokensToTransfer">
		/// The number of tokens that transfer when this transition fires
		/// </param>
		/// <preconditions>
		/// tokensToTransfer must be greater than zero
		/// </preconditions>
		public Transition(string name, Point location, string uid, int tokensToTransfer)
			: base(name, location, uid)
		{
			#region preconditions
			Debug.Assert(tokensToTransfer > 0, "Transition constructed when tokensToTransfer not greater than 0",
				string.Format("Transition constructer called when parameter tokensToTransfer had a value of: {0}", tokensToTransfer));
			#endregion

			inputPlaces = new ObservableCollection<Place>();
			outputPlaces = new ObservableCollection<Place>();
			this.tokensToTransfer = tokensToTransfer;
		}


		/// <summary>
		/// Get the input places to this Transition
		/// </summary>
		public ReadOnlyObservableCollection<Place> InputPlaces
			{ get { return new ReadOnlyObservableCollection<Place>(inputPlaces); } }

		/// <summary>
		/// Get the output places to this Transition
		/// </summary>
		public ReadOnlyObservableCollection<Place> OutputPlaces
			{ get { return new ReadOnlyObservableCollection<Place>(outputPlaces); } }

		/// <summary>
		/// Get or set the Tokens to transfer when this transition fires
		/// </summary>
		/// <remarks>
		/// To fire all input places must have atleast this number of tokens
		/// When fired all output places will recieve this number of tokens
		/// </remarks>
		/// <precondtions>
		/// On set: value must be greater than 0
		/// </precondtions>
		public int TokensToTransfer
		{
			get { return tokensToTransfer; }
			set
			{
				#region preconditions
				Debug.Assert(value > 0, "Token To Transfer set with value not greater than zero",
					String.Format("Transition: {0}, had TokensToTransfer set with value: {1}", Name, value));
				#endregion
				tokensToTransfer = value;

				RaisePropertyChanged("TokensToTransfer");

				if (TokensToTransferChanged != null)
					TokensToTransferChanged();
			}

		}


		/// <summary>
		/// Fires this transition
		/// </summary>
		/// <preconditions>
		/// This Transition must be able to fire
		/// </preconditions>
		public void Fire()
		{
			#region preconditions
			Debug.Assert(CanFire, 
				String.Format("Transition: {0} cannot fire", Name),
				String.Format("Transition: {0} had Fire() called when CanFire is false", Name));
			#endregion

			foreach (Place place in inputPlaces)
				place.GiveTokens(TokensToTransfer);
			foreach (Place place in outputPlaces)
				place.TakeTokens(TokensToTransfer);
			
			if (Fired != null)
				Fired();
		}

		/// <summary>
		/// Get whether this Transitions can fire
		/// </summary>
		public Boolean CanFire
		{
			get
			{
				Boolean canFire = true;
				foreach ( Place place in inputPlaces )
					canFire = canFire && place.CanGiveTokens(TokensToTransfer);

				foreach (Place place in outputPlaces)	//May not be necessary will remove if this is deteremined to always be true
					canFire = canFire && place.CanTakeTokens(TokensToTransfer);

				return canFire;
			}
		}


		/// <summary>
		/// Connect a place to this transition as an input
		/// </summary>
		/// <param name="input">The place to connect</param>
		/// <preconditions>
		/// Input cannot already be connected to this transition
		/// </preconditions>
		public void ConnectInputPlace(Place input)
		{
			#region preconditions
			Debug.Assert(!IsConnectedToPlace(input), "Place is already connected to transition", 
				String.Format("Transition: {0} had ConnectInputPlace called with Place: {1}, but {1} is already connected to {0}", Name, input.Name));
			#endregion

			inputPlaces.Add(input);

			RaiseConnectedPlacesChanged();
			if (PlaceConnected != null)
				PlaceConnected(input);
		}

		/// <summary>
		/// Connect a place to this transition as an output
		/// </summary>
		/// <param name="input">The place to connect</param>
		/// <preconditions>
		/// Outpu cannot already be connected to this transition
		/// </preconditions>
		public void ConnectOutputPlace(Place output)
		{
			#region preconditions
			Debug.Assert(!IsConnectedToPlace(output), "Place is already connected to transition", 
				String.Format("Transition: {0} had ConnectOutputPlace called with Place: {1}, but {1} is already connected to {0}", Name, output.Name));
			#endregion

			outputPlaces.Add(output);

			RaiseConnectedPlacesChanged();
			if (PlaceConnected != null)
				PlaceConnected(output);
		}

		/// <summary>
		/// Check if the passed place is connected to this transition as either
		/// an input or output
		/// </summary>
		/// <param name="place">The place to check</param>
		/// <returns>True if the place is connected, false if not</returns>
		/// <remarks>
		/// This can be method is not the fastest and can be rewritten to 
		/// be faster
		/// </remarks>
		public bool IsConnectedToPlace(Place place)
		{
			bool isConnected = false;

			foreach (Place input in inputPlaces)
				isConnected = isConnected || input.Equals(place);

			foreach (Place output in outputPlaces)
				isConnected = isConnected || output.Equals(place);

			return isConnected;
		}

		/// <summary>
		/// Disconnected a place from this transition
		/// </summary>
		/// <param name="place">The place to disconnect</param>
		public void DisconnectPlace(Place place)
		{
			if(inputPlaces.Contains(place))
				inputPlaces.Remove(place);
			else
				outputPlaces.Remove(place);

			RaiseConnectedPlacesChanged();
			if (PlaceDisconnected != null)
				PlaceDisconnected(place);
		}

		#region Object method overrides

		/// <summary>
		/// Returns true if the object passed is equal to this object
		/// </summary>
		/// <param name="obj">The object to check against</param>
		/// <returns>True if equal, false if not</returns>
		public override bool Equals(object obj)
		{
			if ( obj is Transition )
				return base.Equals(obj);

			return false;
		}

		/// <summary>
		/// Serves as a Hash Function for this type
		/// </summary>
		/// <returns>A hash code for the current <code>Transition</code></returns>
		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		#endregion

		/// <summary>
		/// Raises the connected places changed event
		/// </summary>
		protected void RaiseConnectedPlacesChanged()
		{
			if (ConnectedPlacesChanged != null)
				ConnectedPlacesChanged();
		}
	}
}
